/*
Copyright (c) 2014, Rick Wilson
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of 'Taareekah' nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import gameobject.GOManager;
import gameobject.GOHandle;
import world.Terrain;
import world.GenericTerrainMap;
import world.World;
import world.BiomeType;
import world.BiomeMap;
import worldgeneration.WorldGenerator;
import util.Point;
import java.util.logging.Level;
import java.util.logging.Logger;
import parameters.*;


import java.util.Set;
import java.util.HashSet;

def estimateCarryingCapacity(Point p, BiomeMap biome_map) {
    BiomeType type = biome_map.getBiomeAtPoint(p.getX(), p.getY());
    
    if (type.getName() == "grassland") {
        return 1.0f;
    } else if (type.getName() == "coast") {
        return 0.7f;
    } else if (type.getName() == "swamp") {
        return 0.3;
    } else if (type.getName() == "forest") {
        return 0.5f;
    } else {
        return 0.09f;
    }
    
    return 0.09f;
}

def num_starting_civs = scriptparameters.getChild("startingcivs").getValueAsInt();
def starting_pop = scriptparameters.getChild("startingpop").getValueAsInt();
def min_dist_from_site = scriptparameters.getChild("mindistfromsite").getValueAsInt();
def max_dist_from_site = scriptparameters.getChild("maxdistfromsite").getValueAsInt();
def max_sites_per_civ = scriptparameters.getChild("maxsitesperciv").getValueAsInt();
def death_rate = scriptparameters.getChild("deathrate").getValueAsFloat();
def birth_rate = scriptparameters.getChild("birthrate").getValueAsFloat();
def num_ticks = scriptparameters.getChild("numticks").getValueAsInt();

def size = worldgenerator.getWorldSize();
def seed = worldgenerator.getSeed();
def civilizations = new HashSet<GOHandle>();
def terrain = worldgenerator.getTerrain();
def biome_map = terrain.getBiomeMap();
Random rand = new Random(seed);
Set<Point> unavailable = new HashSet<>();

def printCivs(String filename) {
}

def clamp(int value, int min, int max) {
    if (value < min) {
        return min;
    }
    
    if (value > max) {
        return max;
    }
    
    return value;
}


def chooseRandomPoint(BiomeMap biome_map, Random rand, Set<Point> unavailable, int size) {
    Point p = new Point(-1,-1);
    int trys = 100;
    while (trys > 0) {
        p.set(rand.nextInt(size), rand.nextInt(size));
        if (unavailable.contains(p)) {
            trys--;
            continue;
        }
        
        if(biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "ocean" || biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "null" || biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "mountain") {
            trys--;
            continue;
        }
        unavailable.add(p);
        return p;
    }
    
    return p;
}


def spawnNewSiteFromSite(GOHandle from_site, BiomeMap biome_map, Random rand, Set<Point> unavailable, int size, float death_rate, float birth_rate, int min_dist, int max_dist) {

    GOHandle civ_handle = from_site.getAttribute("InhabitedSite.civilization");
    int amount_to_take = Math.round((float)((int)from_site.getAttribute("InhabitedSite.fertile_population")) * 0.5f);
    Point from_point = (Point)from_site.getAttribute("InhabitedSite.location");

    Point p = new Point(-1,-1);
    int trys = 100;
    while (trys > 0) {
        int new_x = clamp(from_point.getX() + rand.nextInt(2 * max_dist) - max_dist, 0, size - 1);
        int new_y = clamp(from_point.getY() + rand.nextInt(2 * max_dist) - max_dist, 0, size - 1);
        p.set(new_x, new_y);
        
        if (unavailable.contains(p)) {
            trys--;
            continue;
        }
        
        if(biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "ocean" || biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "null" || biome_map.getBiomeAtPoint(p.getX(), p.getY()).getName() == "mountain") {
            trys--;
            continue;
        }
        
        from_site.setAttribute("InhabitedSite.fertile_population", (int)from_site.getAttribute("InhabitedSite.fertile_population") - amount_to_take);
        from_site.setAttribute("InhabitedSite.population", (int)from_site.getAttribute("InhabitedSite.fertile_population") + (int)from_site.getAttribute("InhabitedSite.child_population"));
        unavailable.add(p);
        GOHandle site_handle = gomanager.createGO("InhabitedSite");
        
        site_handle.setAttribute("InhabitedSite.fertile_population", amount_to_take);
        site_handle.setAttribute("InhabitedSite.child_population", (int)0);
        site_handle.setAttribute("InhabitedSite.population", amount_to_take);
        site_handle.callMethod("InhabitedSite.setLocation", p);
        //site_handle.setAttribute("InhabitedSite.location", p);
        site_handle.setAttribute("InhabitedSite.deathrate", death_rate);
        site_handle.setAttribute("InhabitedSite.birthrate", birth_rate);
        civ_handle.callMethod("Civilization.addSite", site_handle);

        return site_handle;
    }
    
    return null;
}

for (int i = 0; i < num_starting_civs; i++) {
    Point p = chooseRandomPoint(biome_map, rand, unavailable, size);
    if(p.getX() < 0) {
        Logger.getLogger("CivilizationGenerator").log(Level.SEVERE, "Couldnt find a point");
        break;
    }
    GOHandle civ_handle = gomanager.createGO("Civilization");
    
    civ_handle.setAttribute("Civilization.civilization_name", String.valueOf(i));
    civ_handle.setAttribute("Civilization.total_population", starting_pop);
    
    GOHandle site_handle = gomanager.createGO("InhabitedSite");
    
    site_handle.setAttribute("InhabitedSite.fertile_population", starting_pop);
    site_handle.setAttribute("InhabitedSite.child_population", (int)0);
    site_handle.setAttribute("InhabitedSite.population", starting_pop);
        site_handle.callMethod("InhabitedSite.setLocation", p);
        //site_handle.setAttribute("InhabitedSite.location", p);
    site_handle.setAttribute("InhabitedSite.deathrate", death_rate);
    site_handle.setAttribute("InhabitedSite.birthrate", birth_rate);
    civ_handle.callMethod("Civilization.addSite", site_handle);
    civ_handle.setAttribute("Civilization.capital_site", site_handle);
    civilizations.add(civ_handle);
    
}
Logger.getLogger("CivilizationGenerator").log(Level.SEVERE, "Created civs");
Set<GOHandle> sites_to_grow = new HashSet<>();
Set<GOHandle> sites_to_remove = new HashSet<>();
for (int i = 0; i < num_ticks; i++) {
    if (i % 100 == 0) {
        Logger.getLogger("CivilizationGenerator").log(Level.SEVERE, "tick {0}", String.valueOf(i));
    }
    sites_to_grow.clear();
    for (GOHandle civ_handle : civilizations) {
        sites_to_remove.clear();
        Set<GOHandle> sites = (Set<GOHandle>)civ_handle.getAttribute("Civilization.all_sites");
       
        int new_total_pop = 0;
        for (GOHandle site_handle : sites) {
            float mod = estimateCarryingCapacity((Point)site_handle.getAttribute("InhabitedSite.location"), biome_map);
            int site_new_pop = site_handle.callMethod("InhabitedSite.tickLog", biome_map);
            if (site_new_pop <= 0) {
                sites_to_remove.add(site_handle);
                continue;
            }
            
            if ((int)site_handle.getAttribute("InhabitedSite.fertile_population") > 1000) {
                sites_to_grow.add(site_handle);
            }
            
            new_total_pop += site_new_pop;
        }
        
        for (GOHandle site_handle : sites_to_remove) {
            sites.remove(site_handle);
            unavailable.remove((Point)site_handle.getAttribute("InhabitedSite.location"));
            site_handle.deregister();
        }
        
        civ_handle.setAttribute("Civilization.total_population", new_total_pop);
        civ_handle.setAttribute("Civilization.all_sites", sites);
    }
    
    for (GOHandle site_handle : sites_to_grow) {
        GOHandle civ = (GOHandle)site_handle.getAttribute("InhabitedSite.civilization");
        Set<GOHandle> sites = (Set<GOHandle>)civ.getAttribute("Civilization.all_sites");
        if(sites.size() >= max_sites_per_civ) {
            continue;
        }
        spawnNewSiteFromSite(site_handle, biome_map, rand, unavailable, size, death_rate, birth_rate, max_dist_from_site, max_dist_from_site);
    }
}

    PrintWriter output = new PrintWriter(new FileWriter("civs.txt"));
    for (GOHandle civ_handle : civilizations) {
        output.println("\n------------------------");
        Set<GOHandle> sites = (Set<GOHandle>)civ_handle.getAttribute("Civilization.all_sites");
        String civ_name = (String)civ_handle.getAttribute("Civilization.civilization_name");
        output.println("Name: " + civ_name);
        for (GOHandle site_handle : sites) {
            output.println("\n------------------------");
            output.println("----Total Population: " + String.valueOf((int)site_handle.getAttribute("InhabitedSite.population")));
            output.println("----Fertile Population: " + String.valueOf((int)site_handle.getAttribute("InhabitedSite.fertile_population")));
            output.println("----Child Population: " + String.valueOf((int)site_handle.getAttribute("InhabitedSite.child_population")));
            Point p = (Point)site_handle.getAttribute("InhabitedSite.location");
            
            output.println("----Location: " + String.valueOf(p.getX()) + ", " + String.valueOf(p.getY()));
        }
    }
    output.close();